Support registry tab and servers V1#1632
Conversation
|
🚅 Environment inspector-pr-1632 in triumphant-alignment has no services deployed. 1 service not affected by this PR
|
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
284adc2 to
69e4070
Compare
|
Caution Review failedPull request was closed or merged during review WalkthroughIntroduces a registry feature-flagged tab and catalog browser. Adds a RegistryTab component that renders MCP servers from a catalog with connection/disconnection controls and starring capability. Extends ServersTab with Quick Connect integration, featured catalog card selection, and registry navigation. Implements useRegistryServers hook to fetch, cache, and manage catalog cards alongside workspace connection state. Updates OAuth flow to support registry-aware token routing through Convex endpoints. Adds HTTP client utilities for catalog/star operations and persists pending quick-connect state in localStorage. Includes comprehensive test coverage for new components and utilities. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (2)
mcpjam-inspector/client/src/hooks/__tests__/use-server-state.test.tsx (1)
6-12: Prefer repository mock presets over ad-hoc test doubles.This hoisted mock setup works, but client tests are easier to maintain when built from shared mock presets rather than local one-offs.
As per coding guidelines:
mcpjam-inspector/client/**/__tests__/*.test.{ts,tsx}: Use mock presets from client/src/test/mocks/ including mcpApiPresets and storePresets in client tests.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@mcpjam-inspector/client/src/hooks/__tests__/use-server-state.test.tsx` around lines 6 - 12, Replace the ad-hoc hoisted mock for toastError, toastSuccess, and handleOAuthCallbackMock with the shared mock presets used across client tests: import and apply the mcpApiPresets and storePresets rather than defining vi.hoisted locals; remove the vi.hoisted block and wire the preset-provided mocks (the presets expose equivalents for toastError/toastSuccess and OAuth handlers) so the test uses the centralized mocks and stays consistent with other tests.mcpjam-inspector/client/src/components/__tests__/RegistryTab.test.tsx (1)
6-19: Use the shared client test presets here.This suite hand-rolls the hook response instead of the standard client presets, so it drifts from the mocked API/store contract the rest of the client tests rely on.
As per coding guidelines "Use mock presets from
client/src/test/mocks/includingmcpApiPresetsandstorePresetsin client tests".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@mcpjam-inspector/client/src/components/__tests__/RegistryTab.test.tsx` around lines 6 - 19, Replace the hand-rolled mockHookReturn in RegistryTab.test.tsx with the shared test presets: import mcpApiPresets and storePresets from client/src/test/mocks and use their registry server data to populate the mocked useRegistryServers return value; keep mockConnect and mockDisconnect as vi.fn() but set registryServers, categories and isLoading from the presets so the mocked hook (useRegistryServers) conforms to the standard client API/store contract used elsewhere.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@mcpjam-inspector/client/src/components/__tests__/RegistryTab.test.tsx`:
- Around line 74-85: The tests leave localStorage state behind (the
'registry-pending-redirect' key) so make the beforeEach also reset storage;
update the beforeEach in RegistryTab.test.tsx (where mockConnect, mockDisconnect
and mockHookReturn are set) to call
localStorage.removeItem('registry-pending-redirect') or localStorage.clear() so
each test starts with a clean storage state and is order-independent.
In `@mcpjam-inspector/client/src/components/RegistryTab.tsx`:
- Around line 81-92: The code sets localStorage key "registry-pending-redirect"
before calling connect(server) in handleConnect but never clears it if connect
rejects, leaving a stale redirect value; add a catch block around await
connect(server) (or move the setItem to after a successful connect) so that on
rejection you removeItem("registry-pending-redirect") and rethrow the error (or
otherwise handle it) — reference handleConnect, connect(server) and the
"registry-pending-redirect" localStorage key.
In `@mcpjam-inspector/client/src/components/ServersTab.tsx`:
- Around line 532-546: The Card element currently uses onClick for quick-connect
but is not keyboard-accessible; change the interactive Card to be a semantic
button (or render it as a <button> via the Card component API) or add
role="button", tabIndex={0}, and an onKeyDown handler that triggers onConnect
for Enter and Space; ensure you pass type="button" (if rendering a real button)
and preserve the same onConnect payload (name: server.displayName, type: "http",
url: server.transport.url, useOAuth, oauthScopes, clientId, registryServerId:
server._id) and add an accessible label/aria-label using server.displayName.
In `@mcpjam-inspector/client/src/hooks/useRegistryServers.ts`:
- Around line 5-10: DEV_MOCK_REGISTRY is currently hardwired to
import.meta.env.DEV && true which keeps mock mode always enabled in development
and prevents real Convex queries from running; change the constant to only
enable mocks when an explicit opt-in flag is set (e.g.,
import.meta.env.VITE_DEV_MOCK_REGISTRY === 'true' or similar) so that in normal
dev the code uses real Convex paths, and only when the new env flag is present
will useRegistryServers fall back to mocks; update the DEV_MOCK_REGISTRY
declaration and any code paths using it (useRegistryServers) to check the
explicit flag and default to false.
- Around line 248-283: The UI is showing servers as "not_connected" while
connections are still unresolved because connectedRegistryIds defaults to an
empty Set; update the loading condition so it also waits for connections before
rendering real server states by modifying isLoading (which currently only checks
registryServers) to include connections (or the presence of
connectedRegistryIds) in its readiness check; reference the
connectedRegistryIds, connections, registryServers, and isLoading symbols so you
locate and change the loading logic accordingly.
In `@mcpjam-inspector/client/src/lib/oauth/mcp-oauth.ts`:
- Around line 477-483: The code is directly calling JSON.parse on localStorage
values (e.g., the storedOAuthConfig -> registryServerId extraction) outside any
try/catch which can throw on malformed data; create a small helper (e.g.,
safeParseOAuthConfig(key) or parseStoredOAuthConfig) that reads
localStorage.getItem, wraps JSON.parse in try/catch, and returns undefined on
parse failure, then replace the direct JSON.parse usages (the registryServerId
extraction and the other similar call in the file around the 636-642 area) to
use that helper so malformed/stale values yield undefined instead of throwing.
---
Nitpick comments:
In `@mcpjam-inspector/client/src/components/__tests__/RegistryTab.test.tsx`:
- Around line 6-19: Replace the hand-rolled mockHookReturn in
RegistryTab.test.tsx with the shared test presets: import mcpApiPresets and
storePresets from client/src/test/mocks and use their registry server data to
populate the mocked useRegistryServers return value; keep mockConnect and
mockDisconnect as vi.fn() but set registryServers, categories and isLoading from
the presets so the mocked hook (useRegistryServers) conforms to the standard
client API/store contract used elsewhere.
In `@mcpjam-inspector/client/src/hooks/__tests__/use-server-state.test.tsx`:
- Around line 6-12: Replace the ad-hoc hoisted mock for toastError,
toastSuccess, and handleOAuthCallbackMock with the shared mock presets used
across client tests: import and apply the mcpApiPresets and storePresets rather
than defining vi.hoisted locals; remove the vi.hoisted block and wire the
preset-provided mocks (the presets expose equivalents for
toastError/toastSuccess and OAuth handlers) so the test uses the centralized
mocks and stays consistent with other tests.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 7fdbe88c-2ca6-4f8d-b092-24f7af6bb0c1
📒 Files selected for processing (13)
mcpjam-inspector/client/src/App.tsxmcpjam-inspector/client/src/components/RegistryTab.tsxmcpjam-inspector/client/src/components/ServersTab.tsxmcpjam-inspector/client/src/components/__tests__/RegistryTab.test.tsxmcpjam-inspector/client/src/components/mcp-sidebar.tsxmcpjam-inspector/client/src/hooks/__tests__/use-server-state.test.tsxmcpjam-inspector/client/src/hooks/useRegistryServers.tsmcpjam-inspector/client/src/lib/__tests__/hosted-navigation.test.tsmcpjam-inspector/client/src/lib/__tests__/hosted-tab-policy.test.tsmcpjam-inspector/client/src/lib/hosted-tab-policy.tsmcpjam-inspector/client/src/lib/oauth/__tests__/debug-oauth-client-metadata.test.tsmcpjam-inspector/client/src/lib/oauth/mcp-oauth.tsmcpjam-inspector/shared/types.ts
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (1)
mcpjam-inspector/client/src/components/ServersTab.tsx (1)
177-180:as anytype assertions obscure type safety.The query path string cast and argument cast bypass Convex's type inference. Consider importing the generated
apiobject from your Convex functions and using it directly, which provides compile-time validation of query paths and arguments.💡 Preferred pattern
import { api } from "../../convex/_generated/api"; // ... const registryServers = useQuery( api.registryServers.listRegistryServers, isAuthenticated ? {} : "skip", );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@mcpjam-inspector/client/src/components/ServersTab.tsx` around lines 177 - 180, The useQuery call in ServersTab uses unsafe any casts for the query key and arguments which bypass Convex's type safety; import the generated api (import { api } from "../../convex/_generated/api") and replace the string/any usage with the function reference api.registryServers.listRegistryServers as the first argument to useQuery, and pass a typed argument (use {} when isAuthenticated is true, otherwise "skip") so TypeScript can infer the correct return type for registryServers and remove the ({} as any) and the string cast.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@mcpjam-inspector/client/src/components/ServersTab.tsx`:
- Line 154: The prop onLeaveWorkspace declared on the ServersTab component is
never used—remove it from the props interface/type and from the destructuring in
the ServersTab function to eliminate dead code (ensure there are no references
to onLeaveWorkspace elsewhere in the file or children); if the callback is
actually needed later, instead wire it into the appropriate handler (e.g., pass
to a child or call from a button click), otherwise delete the onLeaveWorkspace
symbol from the component signature and any related imports/uses.
In `@mcpjam-inspector/client/src/hooks/useRegistryServers.ts`:
- Around line 294-313: The connect function currently awaits connectMutation
which, if it throws, prevents onConnect from running; wrap the Convex call in a
try/catch so that errors are caught and do not block the local MCP connection:
call connectMutation(...) inside a try and in the catch log or surface the error
(e.g., via an error handler, toast, or processLogger) while still proceeding to
call onConnect with the same payload; keep the existing
DEV_MOCK_REGISTRY/isAuthenticated/workspaceId guard around the mutation and
ensure you reference the same symbols (connect, connectMutation, onConnect,
DEV_MOCK_REGISTRY, isAuthenticated, workspaceId, RegistryServer) when making the
change.
In `@mcpjam-inspector/client/src/lib/oauth/mcp-oauth.ts`:
- Around line 22-30: getConvexSiteUrl currently unconditionally replaces
".convex.cloud" which can return an incorrect URL for custom domains; update
getConvexSiteUrl to check that (import.meta as any).env.VITE_CONVEX_URL matches
the expected ".convex.cloud" pattern (e.g. using a regex like
/\.convex\.cloud$/) before performing the replacement, and if it doesn't match
return null (or fall back to a validated site URL). Also normalize/trim any
trailing slashes on the input (VITE_CONVEX_URL) so the replacement validation is
reliable; reference the getConvexSiteUrl function and the
VITE_CONVEX_URL/VITE_CONVEX_SITE_URL env vars.
- Around line 90-104: The refresh-detection logic in the fetch wrapper (around
serializeBody and isRefresh) only inspects object bodies and will miss
URL-encoded or JSON string payloads; update the isRefresh check in the function
that calls serializeBody (the block computing isRefresh and endpoint before
calling originalFetch) to also handle string bodies by parsing them (e.g., try
JSON.parse for JSON strings, and fallback to new URLSearchParams for
form-encoded strings) and then checking parsed.grant_type === "refresh_token";
ensure the parsed value is used only for detection and the original serialized
body is still forwarded in the POST body payload formatting logic used when
calling originalFetch.
---
Nitpick comments:
In `@mcpjam-inspector/client/src/components/ServersTab.tsx`:
- Around line 177-180: The useQuery call in ServersTab uses unsafe any casts for
the query key and arguments which bypass Convex's type safety; import the
generated api (import { api } from "../../convex/_generated/api") and replace
the string/any usage with the function reference
api.registryServers.listRegistryServers as the first argument to useQuery, and
pass a typed argument (use {} when isAuthenticated is true, otherwise "skip") so
TypeScript can infer the correct return type for registryServers and remove the
({} as any) and the string cast.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 72f55654-a75a-4d24-9cfb-7d68f747a6d3
📒 Files selected for processing (5)
mcpjam-inspector/client/src/components/RegistryTab.tsxmcpjam-inspector/client/src/components/ServersTab.tsxmcpjam-inspector/client/src/components/__tests__/RegistryTab.test.tsxmcpjam-inspector/client/src/hooks/useRegistryServers.tsmcpjam-inspector/client/src/lib/oauth/mcp-oauth.ts
✅ Files skipped from review due to trivial changes (1)
- mcpjam-inspector/client/src/components/RegistryTab.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- mcpjam-inspector/client/src/components/tests/RegistryTab.test.tsx
| function getConvexSiteUrl(): string | null { | ||
| const siteUrl = (import.meta as any).env?.VITE_CONVEX_SITE_URL; | ||
| if (siteUrl) return siteUrl; | ||
| const cloudUrl = (import.meta as any).env?.VITE_CONVEX_URL; | ||
| if (cloudUrl && typeof cloudUrl === "string") { | ||
| return cloudUrl.replace(".convex.cloud", ".convex.site"); | ||
| } | ||
| return null; | ||
| } |
There was a problem hiding this comment.
getConvexSiteUrl may return malformed URL for non-standard deployments.
If VITE_CONVEX_URL contains a custom domain (not .convex.cloud), the replacement produces an unchanged URL that won't route to Convex HTTP actions. Consider validating the replacement succeeded or returning null when the pattern doesn't match.
🛠️ Suggested refinement
function getConvexSiteUrl(): string | null {
const siteUrl = (import.meta as any).env?.VITE_CONVEX_SITE_URL;
if (siteUrl) return siteUrl;
const cloudUrl = (import.meta as any).env?.VITE_CONVEX_URL;
if (cloudUrl && typeof cloudUrl === "string") {
- return cloudUrl.replace(".convex.cloud", ".convex.site");
+ if (cloudUrl.includes(".convex.cloud")) {
+ return cloudUrl.replace(".convex.cloud", ".convex.site");
+ }
}
return null;
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@mcpjam-inspector/client/src/lib/oauth/mcp-oauth.ts` around lines 22 - 30,
getConvexSiteUrl currently unconditionally replaces ".convex.cloud" which can
return an incorrect URL for custom domains; update getConvexSiteUrl to check
that (import.meta as any).env.VITE_CONVEX_URL matches the expected
".convex.cloud" pattern (e.g. using a regex like /\.convex\.cloud$/) before
performing the replacement, and if it doesn't match return null (or fall back to
a validated site URL). Also normalize/trim any trailing slashes on the input
(VITE_CONVEX_URL) so the replacement validation is reliable; reference the
getConvexSiteUrl function and the VITE_CONVEX_URL/VITE_CONVEX_SITE_URL env vars.
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
mcpjam-inspector/client/src/hooks/use-server-state.ts (1)
41-75:⚠️ Potential issue | 🟠 MajorPreserve
registryServerIdoutside the first OAuth attempt.Line 720 fixes the initial
initiateOAuth()call, but the rest of the flow still drops this value.saveOAuthConfigToLocalStorage()rewritesmcp-oauth-config-*withoutregistryServerId, and theforceOAuthFlowbranch at Lines 1275-1292 clears storage before re-starting OAuth with only{ serverName, serverUrl }. After an edit, a cached-token reconnect, or a forced re-auth,mcpjam-inspector/client/src/lib/oauth/mcp-oauth.tsloses the registry context and falls back to the generic proxy instead of/registry/oauth/*.Also applies to: 653-725, 1249-1292
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@mcpjam-inspector/client/src/hooks/use-server-state.ts` around lines 41 - 75, saveOAuthConfigToLocalStorage is dropping registryServerId from stored oauth data which causes mcp-oauth.ts to lose registry context; update saveOAuthConfigToLocalStorage to include formData.registryServerId (when present) into the oauthConfig object (and into clientInfo if appropriate) before JSON.stringify so the mcp-oauth flow can read it later, and adjust the forceOAuthFlow branch (the code that clears and restarts OAuth) to either preserve registryServerId in storage or re-write it when re-starting (keep keys `mcp-oauth-config-${formData.name}` and `mcp-client-${formData.name}` populated with registryServerId so `/registry/oauth/*` routing remains available).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@mcpjam-inspector/client/src/components/RegistryTab.tsx`:
- Around line 57-64: RegistryTab is pulling categories from useRegistryServers
but never applies them when rendering consolidatedServers; add a UI control
(e.g., a category select or tabs) in RegistryTab that stores the
selectedCategory in local state and filter consolidatedServers by that
selectedCategory before mapping/rendering, using the categories array returned
from useRegistryServers to populate the control; ensure the filter logic
(applied just before the render of consolidatedServers) respects a "All" option
and continues to work with existing connect/disconnect handlers.
- Around line 68-77: The redirect marker is stored using server.displayName but
the code that checks it (useEffect in RegistryTab, the localStorage key
"registry-pending-redirect") looks up servers by their internal key/name
supplied to onConnect; update the write and read of the pending-redirect marker
to use the server key/name that connect() passes (the name property from
onConnect in useRegistryServers.connect) instead of displayName so the lookup
servers[pending] and the cleanup both reference the same identifier; locate
usages in RegistryTab (the useEffect that reads "registry-pending-redirect" and
the code that sets that key) and change them to persist/read server.name (or the
exact property used in onConnect) consistently.
In `@mcpjam-inspector/client/src/hooks/useRegistryServers.ts`:
- Around line 275-280: The sort comparator used when building "ordered"
currently accepts only one parameter and returns -1/1 which yields inconsistent
ordering; update the comparator used on [...variants].sort(...) to a proper
two-parameter comparator (accepting a and b) that returns 0 when a.clientType
=== b.clientType and otherwise returns -1 when a.clientType === "app" and 1
otherwise; locate the sort call that defines "ordered" inside the loop over
groups.values() (and references "variants") and replace the single-arg
comparator with this two-arg comparator to ensure stable, deterministic
ordering.
- Around line 432-443: The disconnect function currently awaits
disconnectMutation and will never call onDisconnect if the mutation rejects, and
it also passes server.displayName which can mismatch connect's
getRegistryServerName(server); modify the disconnect flow in disconnect(server)
to call disconnectMutation inside a try/catch (only when !DEV_MOCK_REGISTRY &&
isAuthenticated && workspaceId) so failures are caught and do not prevent
proceeding, log or swallow the error, and always invoke onDisconnect with
getRegistryServerName(server) (not server.displayName) so the name matches the
connect flow; keep the existing workspace/auth checks around the mutation but
ensure onDisconnect is called unconditionally after the mutation attempt.
- Around line 380-395: The effect currently calls connectMutation(...) and
discards any rejection; update the call inside useEffect so the promise is
handled: after deleting the id from pendingServerIds and invoking
connectMutation({ registryServerId, workspaceId } as any), attach a .catch(err
=> { console.error("connectMutation failed for", registryServerId, err);
setPendingServerIds(prev => { const next = new Map(prev);
next.set(registryServerId, serverName); return next; }); }) (or use async/await
with try/catch) to log the error and re-add the registryServerId to
pendingServerIds so the UI/state can retry; reference the existing symbols
pendingServerIds, liveServers, setPendingServerIds, and connectMutation when
making the change.
In `@mcpjam-inspector/client/src/lib/oauth/__tests__/mcp-oauth.test.ts`:
- Around line 9-29: Update the hoisted mocks so they use the injected fetch
function from the auth(...) call instead of calling window.fetch directly:
modify the mockSdkAuth implementation to accept the options parameter and call
options.fetchFn (or fallback to global fetch) for proxy-response cases, and
ensure mockFetchToken/mockSelectResourceURL (the mocks used by mcp-oauth tests)
also use the provided fetchFn when present; this keeps tests exercising the
proxy path and prevents real network calls.
---
Outside diff comments:
In `@mcpjam-inspector/client/src/hooks/use-server-state.ts`:
- Around line 41-75: saveOAuthConfigToLocalStorage is dropping registryServerId
from stored oauth data which causes mcp-oauth.ts to lose registry context;
update saveOAuthConfigToLocalStorage to include formData.registryServerId (when
present) into the oauthConfig object (and into clientInfo if appropriate) before
JSON.stringify so the mcp-oauth flow can read it later, and adjust the
forceOAuthFlow branch (the code that clears and restarts OAuth) to either
preserve registryServerId in storage or re-write it when re-starting (keep keys
`mcp-oauth-config-${formData.name}` and `mcp-client-${formData.name}` populated
with registryServerId so `/registry/oauth/*` routing remains available).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: ad65afe2-cb57-4551-9668-0110c089ccef
📒 Files selected for processing (13)
mcpjam-inspector/client/src/App.tsxmcpjam-inspector/client/src/components/RegistryTab.tsxmcpjam-inspector/client/src/components/ServersTab.tsxmcpjam-inspector/client/src/components/__tests__/RegistryTab.test.tsxmcpjam-inspector/client/src/hooks/__tests__/consolidateServers.test.tsmcpjam-inspector/client/src/hooks/hosted/use-hosted-oauth-gate.tsmcpjam-inspector/client/src/hooks/use-server-state.tsmcpjam-inspector/client/src/hooks/useRegistryServers.tsmcpjam-inspector/client/src/lib/hosted-oauth-callback.tsmcpjam-inspector/client/src/lib/oauth/__tests__/mcp-oauth.test.tsmcpjam-inspector/client/src/lib/oauth/mcp-oauth.tsmcpjam-inspector/client/src/state/oauth-orchestrator.tsmcpjam-inspector/shared/types.ts
✅ Files skipped from review due to trivial changes (3)
- mcpjam-inspector/client/src/lib/hosted-oauth-callback.ts
- mcpjam-inspector/client/src/hooks/hosted/use-hosted-oauth-gate.ts
- mcpjam-inspector/client/src/components/tests/RegistryTab.test.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- mcpjam-inspector/client/src/components/ServersTab.tsx
| const { registryServers, categories, isLoading, connect, disconnect } = | ||
| useRegistryServers({ | ||
| workspaceId, | ||
| isAuthenticated, | ||
| liveServers: servers, | ||
| onConnect, | ||
| onDisconnect, | ||
| }); |
There was a problem hiding this comment.
Category filtering is still missing from the tab.
Lines 57-58 pull categories, but the render path at Lines 127-149 always shows the full consolidatedServers array and exposes no filter control. That leaves one of the Registry tab’s promised user-facing actions unimplemented.
Also applies to: 79-82, 127-149
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@mcpjam-inspector/client/src/components/RegistryTab.tsx` around lines 57 - 64,
RegistryTab is pulling categories from useRegistryServers but never applies them
when rendering consolidatedServers; add a UI control (e.g., a category select or
tabs) in RegistryTab that stores the selectedCategory in local state and filter
consolidatedServers by that selectedCategory before mapping/rendering, using the
categories array returned from useRegistryServers to populate the control;
ensure the filter logic (applied just before the render of consolidatedServers)
respects a "All" option and continues to work with existing connect/disconnect
handlers.
| for (const variants of groups.values()) { | ||
| // App before text | ||
| const ordered = [...variants].sort((a) => | ||
| a.clientType === "app" ? -1 : 1, | ||
| ); | ||
| result.push({ variants: ordered, hasDualType: variants.length > 1 }); |
There was a problem hiding this comment.
Sort comparator signature is incorrect.
The comparator receives only a, ignoring the second element b. This yields inconsistent results when comparing elements of the same type—potentially producing non-deterministic ordering.
🔧 Proper two-parameter comparator
- const ordered = [...variants].sort((a) =>
- a.clientType === "app" ? -1 : 1,
- );
+ const ordered = [...variants].sort((a, b) =>
+ (a.clientType === "app" ? 0 : 1) - (b.clientType === "app" ? 0 : 1),
+ );📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| for (const variants of groups.values()) { | |
| // App before text | |
| const ordered = [...variants].sort((a) => | |
| a.clientType === "app" ? -1 : 1, | |
| ); | |
| result.push({ variants: ordered, hasDualType: variants.length > 1 }); | |
| for (const variants of groups.values()) { | |
| // App before text | |
| const ordered = [...variants].sort((a, b) => | |
| (a.clientType === "app" ? 0 : 1) - (b.clientType === "app" ? 0 : 1), | |
| ); | |
| result.push({ variants: ordered, hasDualType: variants.length > 1 }); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@mcpjam-inspector/client/src/hooks/useRegistryServers.ts` around lines 275 -
280, The sort comparator used when building "ordered" currently accepts only one
parameter and returns -1/1 which yields inconsistent ordering; update the
comparator used on [...variants].sort(...) to a proper two-parameter comparator
(accepting a and b) that returns 0 when a.clientType === b.clientType and
otherwise returns -1 when a.clientType === "app" and 1 otherwise; locate the
sort call that defines "ordered" inside the loop over groups.values() (and
references "variants") and replace the single-arg comparator with this two-arg
comparator to ensure stable, deterministic ordering.
| useEffect(() => { | ||
| if (!isAuthenticated || !workspaceId || DEV_MOCK_REGISTRY) return; | ||
| for (const [registryServerId, serverName] of pendingServerIds) { | ||
| const liveServer = liveServers?.[serverName]; | ||
| if (liveServer?.connectionStatus === "connected") { | ||
| setPendingServerIds((prev) => { | ||
| const next = new Map(prev); | ||
| next.delete(registryServerId); | ||
| return next; | ||
| }); | ||
| connectMutation({ | ||
| registryServerId, | ||
| workspaceId, | ||
| } as any); | ||
| } | ||
| } |
There was a problem hiding this comment.
Mutation errors in effect are silently discarded.
When connectMutation rejects, the promise is orphaned—no logging, no user feedback. The local connection succeeds while Convex state remains stale.
🛡️ Catch and log the error
setPendingServerIds((prev) => {
const next = new Map(prev);
next.delete(registryServerId);
return next;
});
- connectMutation({
+ connectMutation({
registryServerId,
workspaceId,
- } as any);
+ } as any).catch((err) => {
+ console.error("Failed to persist registry connection:", err);
+ });
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| useEffect(() => { | |
| if (!isAuthenticated || !workspaceId || DEV_MOCK_REGISTRY) return; | |
| for (const [registryServerId, serverName] of pendingServerIds) { | |
| const liveServer = liveServers?.[serverName]; | |
| if (liveServer?.connectionStatus === "connected") { | |
| setPendingServerIds((prev) => { | |
| const next = new Map(prev); | |
| next.delete(registryServerId); | |
| return next; | |
| }); | |
| connectMutation({ | |
| registryServerId, | |
| workspaceId, | |
| } as any); | |
| } | |
| } | |
| useEffect(() => { | |
| if (!isAuthenticated || !workspaceId || DEV_MOCK_REGISTRY) return; | |
| for (const [registryServerId, serverName] of pendingServerIds) { | |
| const liveServer = liveServers?.[serverName]; | |
| if (liveServer?.connectionStatus === "connected") { | |
| setPendingServerIds((prev) => { | |
| const next = new Map(prev); | |
| next.delete(registryServerId); | |
| return next; | |
| }); | |
| connectMutation({ | |
| registryServerId, | |
| workspaceId, | |
| } as any).catch((err) => { | |
| console.error("Failed to persist registry connection:", err); | |
| }); | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@mcpjam-inspector/client/src/hooks/useRegistryServers.ts` around lines 380 -
395, The effect currently calls connectMutation(...) and discards any rejection;
update the call inside useEffect so the promise is handled: after deleting the
id from pendingServerIds and invoking connectMutation({ registryServerId,
workspaceId } as any), attach a .catch(err => { console.error("connectMutation
failed for", registryServerId, err); setPendingServerIds(prev => { const next =
new Map(prev); next.set(registryServerId, serverName); return next; }); }) (or
use async/await with try/catch) to log the error and re-add the registryServerId
to pendingServerIds so the UI/state can retry; reference the existing symbols
pendingServerIds, liveServers, setPendingServerIds, and connectMutation when
making the change.
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
mcpjam-inspector/client/src/lib/oauth/mcp-oauth.ts (1)
639-642:⚠️ Potential issue | 🟡 MinorVestigial
window.fetchrestoration has no effect—or worse.The function creates a local
fetchFnpassed toauth()but never assigns towindow.fetch. This restoration is a no-op under normal conditions. However, if external code legitimately patchedwindow.fetch, this would unexpectedly revert it.🔧 Remove unnecessary restoration
- } finally { - // Restore original fetch - window.fetch = originalFetch; - } + }Also applies to lines 749-752 and 919-922.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@mcpjam-inspector/client/src/lib/oauth/mcp-oauth.ts` around lines 639 - 642, The code restores window.fetch in finally blocks but never overwrites it and can inadvertently clobber legitimate patches; remove the unnecessary originalFetch capture and the finally block assignment (i.e., delete uses of originalFetch and any "window.fetch = originalFetch" in the blocks around the calls that create a local fetchFn and call auth()), and ensure the auth(...) calls rely only on the local fetchFn passed in (leave auth, fetchFn, and any temporary fetch wrapper intact). Also remove any dead variables named originalFetch where they only served to restore window.fetch.
🧹 Nitpick comments (2)
mcpjam-inspector/client/src/hooks/__tests__/useWorkspaces.test.ts (1)
59-94: Add explicit tests for legacy-array andundefinedinputs.Great start, but the function’s public contract includes both legacy array input and
undefined; locking these paths with tests will prevent regressions during backend rollout transitions.Suggested additions
describe("normalizeWorkspaceMembersResult", () => { + it("supports legacy array responses", () => { + const member: WorkspaceMember = { + _id: "member-legacy", + workspaceId: "ws-1", + email: "legacy@example.com", + addedBy: "user-1", + addedAt: 1, + isOwner: false, + isPending: false, + hasAccess: true, + accessSource: "workspace", + canRemove: true, + user: null, + }; + + expect(normalizeWorkspaceMembersResult([member])).toEqual({ + members: [member], + canManageMembers: false, + }); + }); + + it("returns safe defaults for undefined", () => { + expect(normalizeWorkspaceMembersResult(undefined)).toEqual({ + members: [], + canManageMembers: false, + }); + }); + it("supports the current object-shaped backend response", () => {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@mcpjam-inspector/client/src/hooks/__tests__/useWorkspaces.test.ts` around lines 59 - 94, Add explicit unit tests covering the legacy-array and undefined inputs for normalizeWorkspaceMembersResult: create one test that passes an array of WorkspaceMember objects (the legacy shape) and asserts it returns the same normalized { members, canManageMembers } form, and another test that calls normalizeWorkspaceMembersResult(undefined) and asserts it returns { members: [], canManageMembers: false }; place them alongside the existing describe("normalizeWorkspaceMembersResult") block so normalizeWorkspaceMembersResult and the WorkspaceMember shape are exercised to prevent regressions.mcpjam-inspector/client/src/lib/oauth/mcp-oauth.ts (1)
109-138: Payload contains redundant keys.The spread on line 115 includes
grant_type, then line 119 addsgrantTypewith the same value. This duplication (and likewise forredirect_uri/redirectUri, etc.) inflates the payload without benefit.♻️ Cleaner mapping without duplication
function toConvexOAuthPayload( registryServerId: string, fields: OAuthRequestFields, ): Record<string, string> { + const { grant_type, redirect_uri, code_verifier, refresh_token, client_id, client_secret, ...rest } = fields; const payload: Record<string, string> = { registryServerId, - ...fields, + ...rest, }; - if (fields.grant_type) { - payload.grantType = fields.grant_type; - } + if (grant_type) payload.grantType = grant_type; // ... similar for other fields🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@mcpjam-inspector/client/src/lib/oauth/mcp-oauth.ts` around lines 109 - 138, The payload currently spreads the original fields (including snake_case keys) and then adds camelCase aliases, causing duplicate keys; update toConvexOAuthPayload to avoid spreading fields directly and instead build payload starting with registryServerId and only include the mapped camelCase properties (grantType, redirectUri, codeVerifier, refreshToken, clientId, clientSecret) when the corresponding snake_case fields in fields exist, or alternatively spread fields but delete the snake_case keys before adding the camelCase ones; reference the toConvexOAuthPayload function and the fields parameter and the specific keys grant_type/grantType, redirect_uri/redirectUri, code_verifier/codeVerifier, refresh_token/refreshToken, client_id/clientId, client_secret/clientSecret when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@mcpjam-inspector/client/src/hooks/useWorkspaces.ts`:
- Around line 91-95: The branch that handles isWorkspaceMembersQueryResult
should ensure canManageMembers is always a boolean: in the return of that branch
(inside useWorkspaces) coerce value.canManageMembers to a boolean (e.g., using
!!value.canManageMembers or Boolean(value.canManageMembers)) so malformed
payloads can't pass undefined/non-boolean to consumers; update the return for
members/canManageMembers accordingly and keep the use of
isWorkspaceMembersQueryResult as the discriminant.
In `@mcpjam-inspector/client/src/lib/oauth/mcp-oauth.ts`:
- Around line 53-64: The stored OAuth config under the key
`mcp-oauth-config-${serverName}` is being overwritten by
`saveOAuthConfigToLocalStorage`, which writes only `{ scopes, customHeaders }`
and drops `registryServerId` previously written by `initiateOAuth`, causing
`getStoredRegistryServerId` to return undefined and lose routing; fix by
updating `saveOAuthConfigToLocalStorage` to first read and parse the existing
value (using the same `mcp-oauth-config-${serverName}` key), merge the new
fields (`scopes`, `customHeaders`) into the existing object while preserving
`registryServerId` (or replace with a single canonical object structure), then
write the merged object back to localStorage so `getStoredRegistryServerId`,
`initiateOAuth`, and OAuth callbacks see a consistent config.
---
Outside diff comments:
In `@mcpjam-inspector/client/src/lib/oauth/mcp-oauth.ts`:
- Around line 639-642: The code restores window.fetch in finally blocks but
never overwrites it and can inadvertently clobber legitimate patches; remove the
unnecessary originalFetch capture and the finally block assignment (i.e., delete
uses of originalFetch and any "window.fetch = originalFetch" in the blocks
around the calls that create a local fetchFn and call auth()), and ensure the
auth(...) calls rely only on the local fetchFn passed in (leave auth, fetchFn,
and any temporary fetch wrapper intact). Also remove any dead variables named
originalFetch where they only served to restore window.fetch.
---
Nitpick comments:
In `@mcpjam-inspector/client/src/hooks/__tests__/useWorkspaces.test.ts`:
- Around line 59-94: Add explicit unit tests covering the legacy-array and
undefined inputs for normalizeWorkspaceMembersResult: create one test that
passes an array of WorkspaceMember objects (the legacy shape) and asserts it
returns the same normalized { members, canManageMembers } form, and another test
that calls normalizeWorkspaceMembersResult(undefined) and asserts it returns {
members: [], canManageMembers: false }; place them alongside the existing
describe("normalizeWorkspaceMembersResult") block so
normalizeWorkspaceMembersResult and the WorkspaceMember shape are exercised to
prevent regressions.
In `@mcpjam-inspector/client/src/lib/oauth/mcp-oauth.ts`:
- Around line 109-138: The payload currently spreads the original fields
(including snake_case keys) and then adds camelCase aliases, causing duplicate
keys; update toConvexOAuthPayload to avoid spreading fields directly and instead
build payload starting with registryServerId and only include the mapped
camelCase properties (grantType, redirectUri, codeVerifier, refreshToken,
clientId, clientSecret) when the corresponding snake_case fields in fields
exist, or alternatively spread fields but delete the snake_case keys before
adding the camelCase ones; reference the toConvexOAuthPayload function and the
fields parameter and the specific keys grant_type/grantType,
redirect_uri/redirectUri, code_verifier/codeVerifier,
refresh_token/refreshToken, client_id/clientId, client_secret/clientSecret when
making the change.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 2b324a63-6ef2-4c13-80fb-9b83abb0d01d
📒 Files selected for processing (6)
mcpjam-inspector/client/src/components/__tests__/mcp-sidebar-feature-flags.test.tsmcpjam-inspector/client/src/components/mcp-sidebar.tsxmcpjam-inspector/client/src/hooks/__tests__/useWorkspaces.test.tsmcpjam-inspector/client/src/hooks/useWorkspaces.tsmcpjam-inspector/client/src/lib/oauth/__tests__/mcp-oauth.test.tsmcpjam-inspector/client/src/lib/oauth/mcp-oauth.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- mcpjam-inspector/client/src/lib/oauth/tests/mcp-oauth.test.ts
db65d2f to
a8deef6
Compare
33ce70e to
a0840f7
Compare
…embers, address comments
f672d19 to
a39a297
Compare


Summary
Added a new Registry tab that displays pre-configured MCP servers from a registry, allowing users to connect with one click and featuring OAuth integration through Convex HTTP actions.
What changed?
RegistryTabcomponent with server cards showing connection status, OAuth badges, and category filteringuseRegistryServershook to fetch registry data and manage connections with live server state/registry/oauth/*endpoints for registry servers